﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Moq;
using Ploeh.AutoFixture;
using Ploeh.AutoFixture.Kernel;

namespace Mocking
{
	public class AutomockingCustomization : ICustomization
	{
		DoNotMock[] TypesToNotMock;

		public AutomockingCustomization(params DoNotMock[] donotMock)
		{
			TypesToNotMock = donotMock;
		}

		public List<Mocked> Mocks
		{
			get 
			{
				return ConcreteMoqBuilder.Mocks;
			}
		}
		List<Tuple<Type, Func<Mock>>> CustomMockBuilders = new List<Tuple<Type, Func<Mock>>>();

		public void AddMockBuilder<T>(Func<Mock<T>> mockBuilder) where T : class
		{
			CustomMockBuilders.Add(new Tuple<Type, Func<Mock>>(typeof(T), mockBuilder));
		}

		ConcreteMoqBuilder ConcreteMoqBuilder;
		
		#region ICustomization Members

		public void Customize(IFixture fixture)
		{
			ConcreteMoqBuilder = new ConcreteMoqBuilder(TypesToNotMock.Select(ttnm => ttnm.TypeToNotMock).ToArray(), CustomMockBuilders.ToArray());
			fixture.Customizations.Add(ConcreteMoqBuilder);
		}

		#endregion
	}

	public class ConcreteMoqBuilder : ISpecimenBuilder
	{
		Type[] ExcludedTypes;
		public ConcreteMoqBuilder(Type[] typesToExcludeFromMocking, Tuple<Type, Func<Mock>>[] customMockBuilders)
		{
			ExcludedTypes = typesToExcludeFromMocking;
			Mocks = new List<Mocked>();
			if(customMockBuilders!=null)
				CustomMockBuilders = customMockBuilders;
			else
				CustomMockBuilders = new Tuple<Type,Func<Mock>>[0];
		}

		Tuple<Type, Func<Mock>>[] CustomMockBuilders;

		Mock CreateMock(Type type)
		{
			var customBuilder = CustomMockBuilders.Where(cb => cb.Item1 == type).FirstOrDefault();

			if (customBuilder == null)
				return (Mock)Activator.CreateInstance(typeof(Mock<>).MakeGenericType(type));
			else
				return customBuilder.Item2();
		}

		public List<Mocked> Mocks { get; private set; }

		#region ISpecimenBuilder Members

		public object Create(object request, ISpecimenContext context)
		{
			try
			{
				if (request is PropertyInfo 
					&& !((request as PropertyInfo).PropertyType.IsPrimitive)
					&& (request as PropertyInfo).PropertyType != typeof(string)
					&& !ExcludedTypes.Contains((request as PropertyInfo).PropertyType))
				{
					var mock = CreateMock((request as PropertyInfo).PropertyType);

					Mocks.Add(new Mocked(
						request as PropertyInfo,
						(request as PropertyInfo).PropertyType,
						mock));

					return mock.Object;
				}

				if (request is ParameterInfo 
					&& !((request as ParameterInfo).ParameterType.IsPrimitive)
					&& (request as ParameterInfo).ParameterType != typeof(string)
					&& !ExcludedTypes.Contains((request as ParameterInfo).ParameterType))
				{
					var mock = CreateMock((request as ParameterInfo).ParameterType);

					Mocks.Add(new Mocked(
						request as ParameterInfo,
						(request as ParameterInfo).ParameterType,
						mock));

					return mock.Object;
				}

				return new NoSpecimen();
			}
			catch (ArgumentException aex)
			{
				System.Diagnostics.Debug.WriteLine(aex);
				// if we cannot create a mock for it, AutoFixture can probably new one up.
				return new NoSpecimen();
			}
		}

		#endregion
	}

	public class Mocked
	{
		public Mocked(object mockRequest, Type mockedType, Mock mock)
		{
			MockRequest = mockRequest;
			MockedType = mockedType;
			Mock = mock;
		}

		public object MockRequest;
		public Type MockedType;
		public Mock Mock;
	}
}
